﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace SharpDepend.Classes.Threading
{
    public sealed class ThreadPool : IDisposable
    {
        public static int TotalRunningThreadPools { get; private set; }

        class RunningThread : IDisposable
        {
            private List<Action> mMethodsDoubleBuffered;
            private List<Action> mMethods;
            private ManualResetEvent mEvent;
            private bool mRunning;

            internal RunningThread()
            {
                mMethodsDoubleBuffered = new List<Action>();
                mMethods = new List<Action>();
                mEvent = new ManualResetEvent(true);

                mRunning = true;
                SharpDepend.Manager.ThreadManager.RunThread("ThreadPool", (Action)ThreadLoop);
            }

            void ThreadLoop()
            {
                ++ThreadPool.TotalRunningThreadPools;

                var tempEvent = mEvent;
                var tempMethods = mMethods;

                if (tempEvent == null)
                {
                    return;
                }

                while (mRunning)
                {
                    // Wait until one or more posts has been added.
                    tempEvent.WaitOne();
                    lock (this)
                    {
                        if (mEvent == null)
                        {
                            break;
                        }
                        tempMethods.AddRange(mMethodsDoubleBuffered);
                        mMethodsDoubleBuffered.Clear();

                        tempEvent.Reset();
                    }

                    while (tempMethods.Count > 0)
                    {
                        tempMethods[0]();
                        tempMethods.RemoveAt(0);
                    }
                }

                --ThreadPool.TotalRunningThreadPools;
            }
            
            internal void AddPost(Action method)
            {
                lock (this)
                {
                    mMethodsDoubleBuffered.Add(method);

                    mEvent.Set();
                }
            }
            
            public void Dispose()
            {
                mRunning = false;

                var tempEvent = mEvent;
                mEvent = null;
                tempEvent.Set();
                tempEvent.Dispose();
            }
        }
        
        private RunningThread mRunning;

        public void Start()
        {
            lock (this)
            {
                mRunning = new RunningThread();
            }
        }

        public void Stop()
        {
            var running = mRunning;
            mRunning = null;
            if (running != null)
            {
                running.Dispose();
            }
        }
        
        public void AddPost(Action method)
        {
            var running = mRunning;
            if (running != null)
            {
                mRunning.AddPost(method);
            }
        }

        public void Dispose()
        {
            Stop();
        }
    }

    public sealed class ThreadPoolDelayed : IDisposable
    {
        struct DelayedMethod
        {
            public DateTime ToStart;
            public Action Method;
        }

        public static int TotalRunningThreadPools { get; private set; }

        class RunningThread : IDisposable
        {
            private List<DelayedMethod> mDelayedMethodsDoubleBuffered;
            private List<DelayedMethod> mDelayedMethods;
            private ManualResetEvent mEventDelayed;
            private bool mRunning;

            internal RunningThread()
            {
                mDelayedMethodsDoubleBuffered = new List<DelayedMethod>();
                mDelayedMethods = new List<DelayedMethod>();
                mEventDelayed = new ManualResetEvent(true);

                mRunning = true;
                SharpDepend.Manager.ThreadManager.RunThread("ThreadPool delayed", (Action)ThreadLoopDelayed);
            }
            
            void ThreadLoopDelayed()
            {
                ++ThreadPoolDelayed.TotalRunningThreadPools;

                var tempEvent = mEventDelayed;
                var tempMethods = mDelayedMethods;

                if (tempEvent == null)
                {
                    return;
                }

                while (mRunning)
                {
                    // Wait until one or more posts has been added.
                    tempEvent.WaitOne();
                    lock (this)
                    {
                        if (mEventDelayed == null)
                        {
                            break;
                        }
                        tempMethods.AddRange(mDelayedMethodsDoubleBuffered);
                        mDelayedMethodsDoubleBuffered.Clear();

                        tempEvent.Reset();
                    }

                    while (tempMethods.Count > 0)
                    {
#if DEBUG
                        tempMethods[0].Method();
#else
                        try
                        {
                            tempMethods[0].Method();
                        }
                        catch (Exception error)
                        {
                            SharpDepend.Log.Error(this, error.Message);
                        }
#endif

                        tempMethods.RemoveAt(0);
                    }
                }

                --ThreadPoolDelayed.TotalRunningThreadPools;
            }

            internal void AddPost(Action method, TimeSpan delay)
            {
                lock (this)
                {
                    mDelayedMethodsDoubleBuffered.Add(new DelayedMethod { Method = method, ToStart = DateTime.Now + delay });

                    mEventDelayed.Set();
                }
            }

            internal void ManuallySetPost()
            {
                mEventDelayed.Set();
            }

            public void Dispose()
            {
                mRunning = false;
                
                var tempEventDelay = mEventDelayed;
                mEventDelayed = null;
                tempEventDelay.Set();
                tempEventDelay.Dispose();
            }
        }
        
        private RunningThread mRunning;

        public void Start()
        {
            lock (this)
            {
                mRunning = new RunningThread();
            }
        }

        public void Stop()
        {
            var running = mRunning;
            mRunning = null;
            if (running != null)
            {
                running.Dispose();
            }
        }
        
        public void AddPost(Action method, TimeSpan delay)
        {
            var running = mRunning;
            if (running != null)
            {
                mRunning.AddPost(method, delay);
            }
        }
        
        public void Dispose()
        {
            Stop();
        }
    }
}
